namespace SPDeploy.Library.Processing.Processors
{
    using System;
    using Data;
    using Exceptions;
    using Helpers;
    using Library;
    using Microsoft.SharePoint.Administration;
    using Action=SPDeploy.Data.Action;

    public class WebApplicationExtensionProcessor : ProcessorBase<WebApplicationExtension, SPIisSettings>
    {
        #region Properties: public

        [ProcessorSetting(DefaultValue = IgnorableBehaviors.Ignore)]
        public IgnorableBehaviors AlreadyDeleted { get; set; }

        public override string Name
        {
            get { return "WebApplicationExtension"; }
        }

        #endregion

        #region Methods: public

        public override SPIisSettings Install(ProcessorContext ctx, WebApplicationExtension config)
        {
            SPWebApplication webApp = GetParentApplication(ctx);
            Log(LogSeverity.Step, "{0} zone '{1}' for web app '{2}'", Describe(ProcessorModes.Install, config.Action), config.Zone, webApp.Name);
            SPUrlZone zone = ValueTools.ConvertEnum<ZoneType, SPUrlZone>(config.Zone);

            if (config.Action == Action.Lookup)
            {
                return FindExistingZoneSettingOrThrow(webApp, zone);
            }
            if (config.Action == Action.LookupOrCreate)
            {
                SPIisSettings zoneSettings = FindExistingZoneSetting(webApp, zone);
                return zoneSettings ?? AddZone(webApp, zone, config);
            }

            SPIisSettings setting = AddZone(webApp, zone, config);
            return setting;
        }

        public override SPIisSettings Uninstall(ProcessorContext ctx, WebApplicationExtension config)
        {
            SPWebApplication webApp = GetParentApplication(ctx);
            Log(LogSeverity.Step, "{0} zone '{1}' for web app '{2}'", Describe(ProcessorModes.Uninstall, config.Action), config.Zone, webApp.Name);
            SPUrlZone zone = ValueTools.ConvertEnum<ZoneType, SPUrlZone>(config.Zone);

            if (config.Action == Action.Create)
            {
                SPIisSettings zoneSettings = FindExistingZoneSetting(webApp, zone);
                if (zoneSettings != null)
                {
                    RemoveZone(webApp, zone);
                    webApp.Update();
                    ctx.ChildrenDeleted = true;
                    return null;
                }
                else if (AlreadyDeleted == IgnorableBehaviors.Error)
                {
                    throw new DeploymentTargetProcessorException(string.Format("Zone '{0}' could not be found for web app '{1}'", zone, webApp.Name));
                }
            }

            return FindExistingZoneSettingOrThrow(webApp, zone);
        }

        public override SPIisSettings Update(ProcessorContext ctx, WebApplicationExtension oldConfig, WebApplicationExtension newConfig)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Methods: private

        private static SPIisSettings AddZone(SPWebApplication webApp, SPUrlZone zone, WebApplicationExtension config)
        {
            if (!config.PortSpecified)
            {
                throw new DeploymentTargetConfigException("Port must be defined for Web Application Extension creation nodes");
            }
            if (config.UseSSL && !config.SSLPortSpecified)
            {
                throw new DeploymentTargetConfigException("SSLPort must be defined for SSL enabled Web Application Extension creation nodes");
            }
            if (config.UseSSL && config.SSLPort == config.Port)
            {
                throw new DeploymentTargetConfigException("Port and SSLPort must not be the same SSL enabled Web Application Extension creation nodes");
            }
            if(!config.Header.IsEmpty() && config.Header.Contains(":"))
            {
                throw new DeploymentTargetConfigException("Header must not contain a protol declaration or port, such as http://");
            }
            
            SPSecureBinding secureBinding = config.UseSSL ? new SPSecureBinding {Port = config.SSLPort} : null;
            var serverBinding = new SPServerBinding
                                    {
                                        Port = config.Port,
                                        HostHeader = config.Header
                                    };
            var zoneSettings = new SPIisSettings(config.Name, config.AllowAnonymous, config.UseNTLM, serverBinding, secureBinding, null);
            zoneSettings.AuthenticationMode = ValueTools.ConvertEnum<AuthenticationMode, System.Web.Configuration.AuthenticationMode>(config.AuthenticationMode);
            zoneSettings.EnableClientIntegration = config.ClientIntegration;
            if (config.MembershipProvider != null)
            {
                zoneSettings.MembershipProvider = config.MembershipProvider;
            }
            if (config.RoleManager != null)
            {
                zoneSettings.RoleManager = config.RoleManager;
            }
            webApp.IisSettings.Add(zone, zoneSettings);

            AssignZoneAccessMapping(webApp, zone, config);
            webApp.Update();
            webApp.Provision();
            
            return zoneSettings;
        }

        private static void AssignZoneAccessMapping(SPWebApplication webApp, SPUrlZone zone, WebApplicationExtension config)
        {
            string zoneUrl = config.Header.IsEmpty() ? webApp.AlternateUrls.GetResponseUrl(SPUrlZone.Default).Uri.Host : config.Header;
            zoneUrl = (config.UseSSL ? "https://" : "http://") + zoneUrl + ":" + (config.UseSSL ? config.SSLPort : config.Port);
            webApp.AlternateUrls.Add(new SPAlternateUrl(zoneUrl, zone));
            webApp.AlternateUrls.Update();
        }

        private static SPIisSettings FindExistingZoneSetting(SPWebApplication webApp, SPUrlZone zone)
        {
            return webApp.IisSettings[zone];
        }

        private static SPIisSettings FindExistingZoneSettingOrThrow(SPWebApplication webApp, SPUrlZone zone)
        {
            SPIisSettings zoneSettings = FindExistingZoneSetting(webApp, zone);
            if (zoneSettings == null)
            {
                throw new DeploymentTargetProcessorException(string.Format("Zone '{0}' could not be found", zone));
            }
            return zoneSettings;
        }

        private static SPWebApplication GetParentApplication(ProcessorContext ctx)
        {
            var webApp = ctx.ParentResult as SPWebApplication;
            if (webApp == null)
            {
                throw new DeploymentTargetProcessorException("Web application extension processor did not receive a valid web application instance");
            }
            return webApp;
        }

        private static void RemoveZone(SPWebApplication webApp, SPUrlZone zone)
        {
            AdminServiceTools.PreformIisCleanup(webApp, true, new[] {webApp.IisSettings[zone]});
            bool removed = webApp.IisSettings.Remove(zone);
            if (!removed)
            {
                throw new DeploymentTargetProcessorException(string.Format("Zone '{0}' could not be removed", zone));
            }
        }

        #endregion
    }
}